package cn.ivicar.guice;

import java.util.HashMap;
import java.util.Map;

import cn.ivicar.mapper.ibatis.mapper.BaseMapper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.google.inject.Scopes;
import com.google.inject.name.Names;
import com.sun.jersey.api.core.PackagesResourceConfig;
import com.sun.jersey.guice.JerseyServletModule;
import com.sun.jersey.guice.spi.container.servlet.GuiceContainer;

public class GuiceServletModule extends JerseyServletModule {
    private static transient final Logger LOGGER = LoggerFactory.getLogger(GuiceServletModule.class);

    private Map<String, String> params;
    private String resourcePackage;
    private String databasePackage;
    private Map<String, String> bindings;
    private boolean bindResources;

    public void setBindings(Map<String, String> bindings) {
        this.bindings = bindings;
    }

    public Map<String, String> getBindings() {
        return this.bindings;
    }

    public void setResourcePackage(String resourcePackage) {
        this.resourcePackage = resourcePackage;
    }

    public void setDatabasePackage(String databasePackage) {
        this.databasePackage = databasePackage;
    }

    public String getResourcePackage() {
        return this.resourcePackage;
    }

    public String getDatabasePackage() {
        return this.databasePackage;
    }

    public GuiceServletModule(String resourcePackage, String databasePackage,
            Map<String, String> bindings, boolean bindResources) {
        this.setResourcePackage(resourcePackage);
        this.setDatabasePackage(databasePackage);
        this.setBindings(bindings);
        this.bindResources = bindResources;
        this.params = new HashMap<String, String>();
    }

    @Override
    protected void configureServlets() {
        try {
            bindDatabase(this.getBindings());
            if (this.bindResources) {
                bindResource(this.getBindings());
            }
        } catch (ClassNotFoundException e) {
            LOGGER.error(e.getMessage());
        }

        bindExtends();

        this.params.put(PackagesResourceConfig.PROPERTY_PACKAGES, getResourcePackage());
        serve("/*").with(GuiceContainer.class, this.params);
    }

    protected void bindExtends() {
    }

    protected void bindResource(Map<String, String> bindings) throws ClassNotFoundException {
        if (bindings != null) {
            for (Map.Entry<String, String> item : bindings.entrySet()) {
                bindResource(item.getKey(), item.getValue());
            }
        }
    }

    protected void bindDatabase(Map<String, String> bindings) throws ClassNotFoundException {
        if (bindings != null) {
            for (Map.Entry<String, String> item : bindings.entrySet()) {
                bindDatabase(item.getKey(), item.getValue());
            }
        }
    }

    @SuppressWarnings({ "rawtypes", "unchecked" })
    protected void bindResource(String domainObjectName, String subDomain)
            throws ClassNotFoundException {
        if (subDomain.length() != 0) {
            subDomain = subDomain + ".";
        }

        ClassLoader cl = this.getClass().getClassLoader();

        String resourceName = getResourcePackage() + ".resource." + subDomain + domainObjectName + "Resource";
        Class resourceClass = Class.forName(resourceName, true, cl);
        bind(resourceClass);
    }

    @SuppressWarnings({ "rawtypes", "unchecked" })
    protected void bindDatabase(String domainObjectName, String subDomain)
            throws ClassNotFoundException {
        if (subDomain.length() != 0) {
            subDomain = subDomain + ".";
        }

        ClassLoader cl = this.getClass().getClassLoader();
        String mapperName = getDatabasePackage() + ".ibatis.mapper." + subDomain + domainObjectName
                + "Mapper";
        String managerName = getDatabasePackage() + ".service." + subDomain + domainObjectName
                + "Manager";
        String managerImplName = getDatabasePackage() + ".service.impl." + subDomain
                + domainObjectName + "ManagerImpl";

        Class mapperClass = Class.forName(mapperName, true, cl);
        Class managerClass = Class.forName(managerName, true, cl);
        Class managerImplClass = Class.forName(managerImplName, true, cl);

        bind(managerClass).to(managerImplClass).in(Scopes.SINGLETON);
        bind(managerClass).annotatedWith(Names.named(domainObjectName + "Manager"))
                .to(managerImplClass).in(Scopes.SINGLETON);
        bind(BaseMapper.class).annotatedWith(Names.named(domainObjectName + "Mapper"))
                .to(mapperClass).in(Scopes.SINGLETON);
    }
}
